#include <linux/module.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/kfifo.h>

struct tiny_tty {
	struct tty_port port;
	struct kfifo fifo;
	int index;
	struct timer_list timer;
};

struct tiny_tty tty0;
struct tty_driver *tiny_tty_driver;

static int tiny_open(struct tty_struct *tty, struct file *filp)
{
	printk(KERN_INFO "%s :%d\n", __func__, tty->index);
	return 0;
}

static void tiny_close(struct tty_struct *tty, struct file *filp)
{
	printk(KERN_INFO "%s\n", __func__);
}

static int tiny_write(struct tty_struct *tty, const unsigned char *buf,
		      int count)
{
	int ret = kfifo_in(&tty0.fifo, buf, count);
	mod_timer(&tty0.timer, jiffies + HZ / 100);
	return ret;
}

static int tiny_write_room(struct tty_struct *tty)
{
	return kfifo_avail(&tty0.fifo);
}

static const struct tty_operations tiny_ops = {
	.open = tiny_open,
	.close = tiny_close,
	.write = tiny_write,
	.write_room = tiny_write_room,
};

void callback(struct timer_list *timer_list)
{
	struct tiny_tty *t;
	char buf[100];
	int cnt = 0;

	t = container_of(timer_list, struct tiny_tty, timer);
	cnt = kfifo_out(&t->fifo, buf, sizeof buf);
	if (cnt > 0) {
		printk(KERN_INFO "%s flip\n", __func__);
		tty_insert_flip_string(&t->port, buf, cnt);
		tty_flip_buffer_push(&t->port);
	}
}

static int __init tiny_tty_init(void)
{
	int result;

	printk(KERN_ALERT "USB TINY device init\n");

	tiny_tty_driver = alloc_tty_driver(1);
	if (!tiny_tty_driver)
		return -ENOMEM;

	tiny_tty_driver->owner = THIS_MODULE;
	tiny_tty_driver->driver_name = "usbtiny";
	tiny_tty_driver->name = "ttytiny";
	tiny_tty_driver->major = 0;
	tiny_tty_driver->minor_start = 0;
	tiny_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
	tiny_tty_driver->subtype = SERIAL_TYPE_NORMAL;
	tiny_tty_driver->flags =
	    TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV |
	    TTY_DRIVER_UNNUMBERED_NODE;
	tiny_tty_driver->init_termios = tty_std_termios;
	tiny_tty_driver->init_termios.c_cflag =
	    B9600 | CS8 | CREAD | HUPCL | CLOCAL;

	tty_set_operations(tiny_tty_driver, &tiny_ops);
	result = tty_register_driver(tiny_tty_driver);

	tty0.index = 0;
	tty_port_init(&(tty0.port));

	//printk(KERN_ALERT "driver have %d devices\n",tiny_tty_driver->);
	tty_port_register_device(&tty0.port, tiny_tty_driver, tty0.index, NULL);


	timer_setup(&tty0.timer, callback, 1);

	result = kfifo_alloc(&tty0.fifo, 1024, GFP_KERNEL);
	if (result != 0) {
		pr_err("kfifo_alloc error:%d\n", result);
	}

	return result;
}

static void __exit tiny_tty_exit(void)
{
	/* 删除 timer */
	del_timer(&tty0.timer);
	printk(KERN_ALERT "del_timer \n");

	/* 删除device */
	tty_unregister_device(tiny_tty_driver, tty0.index);
	printk(KERN_ALERT "tty_unregister_device \n");

	/* 删除driver */
	tty_unregister_driver(tiny_tty_driver);
	printk(KERN_ALERT "USB TINY device tty_unregister_driver\n");
}

module_init(tiny_tty_init);
module_exit(tiny_tty_exit);
MODULE_LICENSE("GPL");
